package org.iabako.server.dao.business;

import org.iabako.server.dao.user.EnterpriseDAO;
import org.iabako.server.serviceimpl.security.SessionUser;
import org.iabako.server.tools.BundleMessages;
import org.iabako.server.tools.ServerTools;
import org.iabako.shared.entity.business.Notification;
import org.iabako.shared.entity.business.NotificationConfig;
import org.iabako.shared.entity.business.NotificationEmail;
import org.iabako.shared.entity.enumeration.NotificationType;
import org.iabako.shared.entity.user.Enterprise;
import org.iabako.shared.entity.user.User;
import org.iabako.shared.tools.GenericTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.*;

/**
 * Created by jose on 13/03/15.
 */

@Repository("notificationDAO")
public class NotificationDAO {

    @PersistenceContext(unitName = "MyPersistenceUnit")
    protected EntityManager entityManager;

    @Autowired
    BundleMessages messages;
    @Autowired
    SessionUser sessionUser;
    @Autowired
    EnterpriseDAO enterpriseDAO;

    public Notification getById(Long idNotification) {
        return entityManager.find(Notification.class, idNotification);
    }

    public Notification merge(Notification notification) {
        return entityManager.merge(notification);
    }

    public List<Notification> calculateNotifications(Enterprise enterprise, NotificationType... notificationType) {

        enterprise.setChildrenEnterprises(enterpriseDAO.getChildrenEnterprises(enterprise));
        enterprise.setAllRelatedEnterprise(enterpriseDAO.getAllRelatedEnterprises(enterprise));
        List<NotificationType> notificationsType = notificationType != null ? Arrays.asList(notificationType) : new ArrayList<NotificationType>();

        ServerTools serverTools = new ServerTools();
        List<Notification> notifications = new ArrayList<Notification>();
        Date today = serverTools.resetTime(new Date());

        String query;
        Long count;

        NotificationConfig notificationConfig = enterprise.getNotificationConfig();

        if (notificationConfig.isNewPastExpenses() &&
                (notificationType == null || notificationsType.contains(NotificationType.newPastExpenses))) {
            query = "SELECT COUNT(e) FROM Expense e WHERE e.paymentDate = :today AND e.paymentMethod IS NULL AND e.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("today", today).setParameter("enterprises", enterprise.getVisibleEnterprisesFinancialData()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.newPastExpenses, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.isNewPastInstallments() &&
                (notificationType == null || notificationsType.contains(NotificationType.newPastInstallments))) {
            query = "SELECT COUNT(i) FROM Installment i JOIN i.sale sale WHERE i.date = :today AND sale.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("today", today).setParameter("enterprises", enterprise.getVisibleEnterprisesFinancialData()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.newPastInstallments, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.isStockRupture() &&
                (notificationType == null || notificationsType.contains(NotificationType.stockRupture))) {
            query = "SELECT COUNT(p) FROM Product p WHERE p.quantity = 0 AND p.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("enterprises", enterprise.getVisibleEnterprises()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.stockRupture, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.getStockLowerThanLimit() > 0 &&
                (notificationType == null || notificationsType.contains(NotificationType.stockLowerThanLimit))) {
            Double stockLimit = new Double(notificationConfig.getStockLowerThanLimit());
            query = "SELECT COUNT(p) FROM Product p WHERE p.quantity < :stockLimit AND p.quantity > 0 AND p.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("stockLimit", stockLimit).setParameter("enterprises", enterprise.getVisibleEnterprises()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.stockLowerThanLimit, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.getFutureExpenses() > 0 &&
                (notificationType == null || notificationsType.contains(NotificationType.futureExpenses))) {
            Date futureExpensesDate = serverTools.addDaysToDate(today, notificationConfig.getFutureExpenses());
            query = "SELECT COUNT(e) FROM Expense e WHERE e.paymentDate > :today AND e.paymentDate <= :futureExpensesDate " +
                    "AND e.paymentMethod IS NULL AND e.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("today", today).setParameter("futureExpensesDate", futureExpensesDate).
                    setParameter("enterprises", enterprise.getVisibleEnterprisesFinancialData()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.futureExpenses, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.getFutureInstallments() > 0 &&
                (notificationType == null || notificationsType.contains(NotificationType.futureInstallments))) {
            Date futureInstallmentsDate = serverTools.addDaysToDate(today, notificationConfig.getFutureInstallments());
            query = "SELECT COUNT(i) FROM Installment i JOIN i.sale sale WHERE i.date > :today AND i.date <= :futureInstallmentsDate AND sale.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query).setParameter("today", today).setParameter("futureInstallmentsDate", futureInstallmentsDate).
                    setParameter("enterprises", enterprise.getVisibleEnterprisesFinancialData()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.futureInstallments, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.isClientBirthDays() &&
                (notificationType == null || notificationsType.contains(NotificationType.birthday))) {
            Calendar todayCalendar = Calendar.getInstance();
            todayCalendar.setTime(today);
            query = "SELECT COUNT(c) FROM Client c WHERE day(c.birthday) = :day AND month(c.birthday) = :month AND c.enterprise IN (:enterprises)";
            count = (Long) entityManager.createQuery(query)
                    .setParameter("day", todayCalendar.get(Calendar.DAY_OF_MONTH))
                    .setParameter("month", todayCalendar.get(Calendar.MONTH) + 1)
                    .setParameter("enterprises", enterprise.getVisibleEnterprises()).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.birthday, count.intValue(), enterprise);
            }
        }

        if (notificationConfig.isPaymentRequest() &&
                (notificationType == null || notificationsType.contains(NotificationType.paymentRequest))) {
            query = "SELECT COUNT(i) FROM Installment i JOIN i.sale sale WHERE (i.requestBeforeDate =:today OR i.requestAfterDate =:today) AND sale.enterprise IN (:enterprises)";
            List<Enterprise> enterprises = enterprise.getParentEnterprise() == null ? enterprise.getAllRelatedEnterprises() : Arrays.asList(enterprise);
            count = (Long) entityManager.createQuery(query).setParameter("today", today).setParameter("enterprises", enterprises).getSingleResult();
            if (count > 0) {
                addNewNotification(notifications, NotificationType.paymentRequest, count.intValue(), enterprise);
            }
        }

        return notifications;
    }

    private void addNewNotification(List<Notification> notifications, NotificationType notificationType, int cardinal, Enterprise enterprise) {
        Notification notification = new Notification();
        notification.setType(notificationType);
        notification.setCardinal(cardinal);
        notification.setEnterprise(enterprise);
        notifications.add(notification);
    }

    public List<Notification> getNotifications(Enterprise enterprise) {
        String query = "SELECT n FROM Notification n WHERE n.enterprise = :enterprise";
        return entityManager.createQuery(query).setParameter("enterprise", enterprise).getResultList();
    }

    public Notification getByNotificationType(NotificationType notificationType) {
        String query = "SELECT n FROM Notification n WHERE n.type = :notificationType AND n.enterprise = :enterprise";
        List<Notification> notifications = entityManager.createQuery(query)
                .setParameter("notificationType", notificationType)
                .setParameter("enterprise", sessionUser.getUser().getEnterprise()).getResultList();
        return notifications == null || notifications.isEmpty() ? null : notifications.get(0);
    }

    public List<Notification> getEmailNotifications(User u) {
        String query = "SELECT n.notification FROM NotificationEmail n WHERE n.user = :user";
        return entityManager.createQuery(query).setParameter("user", u).getResultList();
    }

    public void saveEmailNotifications(User u, List<Notification> notificationsAfter) {
        if (GenericTools.isEmpty(notificationsAfter)) {
            return;
        }
        String query = "DELETE FROM NotificationEmail n WHERE n.user = :user";
        entityManager.createQuery(query).setParameter("user", u).executeUpdate();

        query = "INSERT INTO NOTIFICATION_EMAIL (ID_USER, ID_NOTIFICATION) VALUES (:user, :notification)";

        for (Notification n : notificationsAfter) {
            entityManager.createNativeQuery(query).setParameter("user", u.getId()).setParameter("notification", n.getId()).executeUpdate();
        }
    }

    public List<NotificationEmail> getNotificationEmailsSentOn(Date date) {
        ServerTools serverTools = new ServerTools();
        date = serverTools.resetTime((Date) date.clone());
        Date datePlusOne = serverTools.addDaysToDate((Date) date.clone(), 1);

        String query = "SELECT email FROM NotificationEmail email " +
                "WHERE email.date >= :date AND email.date < :datePlusOne";

        return entityManager.createQuery(query).setParameter("date", date).setParameter("datePlusOne", datePlusOne).getResultList();
    }
}
